package module

import (
	"leaf/chanrpc"
	"leaf/console"
	"leaf/go"
	"leaf/log"
	"leaf/timer"
	"time"
)

type Skeleton struct {
	GoLen              int
	TimerDispatcherLen int
	AsyncCallLen       int
	ChanRPCServer      *chanrpc.Server
	LogError           log.Function
	g                  *g.Go
	timer              *timer.Dispatcher
	client             *chanrpc.Client
	server             *chanrpc.Server
	commandServer      *chanrpc.Server
	initCallbacks      []func()
	exitCallbacks      []func()
}

func (skeleton *Skeleton) OnPrepare() {
	if skeleton.GoLen < 0 {
		skeleton.GoLen = 0
	}
	if skeleton.TimerDispatcherLen <= 0 {
		skeleton.TimerDispatcherLen = 0
	}
	if skeleton.AsyncCallLen < 0 {
		skeleton.AsyncCallLen = 0
	}

	if skeleton.LogError == nil {
		skeleton.LogError = log.Error
	}

	skeleton.g = g.New(skeleton.GoLen)
	skeleton.timer = timer.NewDispatcher(skeleton.TimerDispatcherLen)
	skeleton.client = chanrpc.NewClient(skeleton.AsyncCallLen)
	skeleton.server = skeleton.ChanRPCServer

	if skeleton.server == nil {
		skeleton.server = chanrpc.NewServer(0)
	}
	skeleton.commandServer = chanrpc.NewServer(0)
}

func (skeleton *Skeleton) OnInit() {
	for _, callback := range skeleton.initCallbacks {
		callback()
	}

	skeleton.initCallbacks = nil
}

func (skeleton *Skeleton) OnExit() {
	for _, callback := range skeleton.exitCallbacks {
		callback()
	}

	skeleton.exitCallbacks = nil
}

func (skeleton *Skeleton) Run(closeSignal chan bool) {
	for {
		select {
		case <-closeSignal:
			skeleton.commandServer.Close()
			skeleton.server.Close()
			skeleton.timer.Close()
			for !skeleton.g.Idle() || !skeleton.client.Idle() {
				skeleton.g.Close()
				skeleton.client.Close()
			}
			return
		case ri := <-skeleton.client.ChanAsynRet:
			skeleton.client.Cb(ri)
		case ci := <-skeleton.server.ChanCall:
			skeleton.server.Exec(ci)
		case ci := <-skeleton.commandServer.ChanCall:
			skeleton.commandServer.Exec(ci)
		case cb := <-skeleton.g.ChanCb:
			skeleton.g.Cb(cb)
		case ti := <-skeleton.timer.ChanTimer:
			skeleton.timer.Invoking(ti)
		}
	}
}

func (skeleton *Skeleton) AfterCall(d time.Duration, cb func()) timer.Timer {
	if skeleton.TimerDispatcherLen == 0 {
		panic("invalid TimerDispatcherLen")
	}

	return skeleton.timer.AfterCall(d, cb)
}

func (skeleton *Skeleton) LoopCall(interval time.Duration, cb func()) timer.Timer {
	if skeleton.TimerDispatcherLen == 0 {
		panic("invalid TimerDispatcherLen")
	}

	return skeleton.timer.LoopCall(interval, cb)
}

func (skeleton *Skeleton) CronCall(cronExpr *timer.CronExpr, cb func()) timer.Timer {
	if skeleton.TimerDispatcherLen == 0 {
		panic("invalid TimerDispatcherLen")
	}

	return skeleton.timer.CronCall(cronExpr, cb)
}

func (skeleton *Skeleton) StopTimer(t timer.Timer) {
	skeleton.timer.Stop(t)
}

func (skeleton *Skeleton) Go(f func(), callback func()) {
	if skeleton.GoLen == 0 {
		panic("invalid GoLen")
	}

	skeleton.g.Go(f, callback)
}

func (skeleton *Skeleton) NewLinearContext() *g.LinearContext {
	if skeleton.GoLen == 0 {
		panic("invalid GoLen")
	}

	return skeleton.g.NewLinearContext()
}

func (skeleton *Skeleton) AsyncCall(server *chanrpc.Server, id interface{}, args ...interface{}) {
	if skeleton.AsyncCallLen == 0 {
		panic("invalid AsyncCallLen")
	}

	skeleton.client.Attach(server)
	skeleton.client.AsynCall(id, args...)
}

func (skeleton *Skeleton) RegisterChanRPC(id interface{}, f interface{}) {
	if skeleton.ChanRPCServer == nil {
		panic("invalid ChanRPCServer")
	}

	skeleton.server.Register(id, f)
}

func (skeleton *Skeleton) RegisterCommand(name string, help string, f interface{}) {
	console.Register(name, help, f, skeleton.commandServer)
}

func (skeleton *Skeleton) AfterInit(callback func()) {
	if callback == nil {
		panic("init callback is nil")
	}

	skeleton.initCallbacks = append(skeleton.initCallbacks, callback)
}

func (skeleton *Skeleton) BeforeExit(callback func()) {
	if callback == nil {
		panic("exit callback is nil")
	}

	skeleton.exitCallbacks = append(skeleton.exitCallbacks, callback)
}
